﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Text;
using System.Globalization;
//using System.Windows.Forms;
using System.Threading;
using System.Net;
using System.Net.NetworkInformation;
using System.IO;
using System.Diagnostics;
using Microsoft.Win32;
using Innovox.NetTools;
using System.Windows.Forms;

namespace Innovox.YDeviceLib
{
    public class YNetManager
	{
		const int DefaultThreadCount = 16;
        public Dictionary<IPAddress, YDevice> Devices { get; set; }
        public Dictionary<string, YDevice> DevicesByMac { get; set; }
	    public List<YDevice> SelectedY1gs { get; set; }
        public List<YDevice> SelectedYfms { get; set; }

        public event EventHandler<YNetDeviceDetectedEventArgs> YDeviceDetected;
        public event EventHandler<YNetScanCompletedEventArgs> YNetScanComplete;
        public event EventHandler<YNetScanProgressEventArgs> YNetScanProgress;
        public event EventHandler<YNetScanLogEventArgs> YNetScanLog;
        public event EventHandler<YNetDeviceReadEventArgs> YDeviceReadComplete;
        public event EventHandler<YNetReadCompletedEventArgs> YNetReadComplete;
        public event EventHandler<YNetDeviceWriteEventArgs> YDeviceWriteComplete;
        public event EventHandler<YNetWriteCompletedEventArgs> YNetWriteComplete;
        public event EventHandler<YNetUpgradeProgressEventArgs> YNetUpgradeProgress;
        public event EventHandler<YNetUpgradeCompletedEventArgs> YNetUpgradeComplete;
        
        // Limits the number of pings happening simultaniously
        public int ThreadCount { get; set; }
		private static Semaphore socketBlock;
        int NumOfIpToScan;
        volatile int NumScannedIp;
        DateTime scanStartTime;
        public int ScanProgress { get { return (NumScannedIp * 100 / NumOfIpToScan); } set { ScanProgress = value; } }
        volatile int updateProgress;
        int updateProgressGoal;
        public bool MultiScanMode { get; set; }
        public ObixEntry[] ObixEntries { get; set; }
        public List<string> Errors;

		public YNetManager()
		{
            Devices = new Dictionary<IPAddress, YDevice>();
            ThreadCount = DefaultThreadCount;
			SelectedY1gs = new List<YDevice>();
            SelectedYfms = new List<YDevice>();
            Errors = new List<string>();
            MultiScanMode = true;
		}

        private void ClearErrors()
        {
            if (Errors != null)
            {
                lock (Errors)
                {
                    Errors.Clear();
                }
            }
        }
        private void AddError(string error)
        {
            if (Errors == null)
                Errors = new List<string>();

            lock (Errors)
            {
                Errors.Add(error);
            }
        }

        private void BuildMacDict()
        {
            DevicesByMac = new Dictionary<string,YDevice>();

            lock (Devices)
            {
                foreach (KeyValuePair<IPAddress, YDevice> kvp in Devices)
                    DevicesByMac.Add(kvp.Value.Modem.MacAddress, kvp.Value);
            }
            AssignDeviceNetLevel();
        }

        private Dictionary<string,int> _macLevelDict;

        private void AssignDeviceNetLevel()
        {
            _macLevelDict = new Dictionary<string, int>();
            //foreach(YDeviceData dev in _YdevCollection)
            foreach (KeyValuePair<IPAddress, YDevice> kvp in Devices)
                if (kvp.Value.Modem.AccesType == ModemAccessType.Master)
                {
                    string mac = kvp.Value.Modem.MacAddress;
                    AssignLevel(kvp.Value, 0);
                }
        }

        private void AssignLevel(YDevice dev, int level)
        {
            dev.Modem.Level = level;
            level++;

            foreach (string mac in dev.Modem.Childs)
            {
                if (DevicesByMac.ContainsKey(mac) && _macLevelDict.ContainsKey(mac) == false)
                {
                    _macLevelDict.Add(mac, level);
                    AssignLevel(DevicesByMac[mac], level);
                }
            }
        }

        private void InitScanThreading()
        {
            NumScannedIp = 0;
            /*lock (Devices)
            {
                Devices.Clear();
            }*/
            socketBlock = new Semaphore(0, ThreadCount);
            socketBlock.Release(ThreadCount);
        }

        private volatile bool _abortScan;
        private volatile CancellationSource _cancelSource;

        public void StopScan()
        {
            _abortScan = true;
            _cancelSource.RequestCancellation();
        }

        public bool BeginScan(string startIp, string endIp)
        {
            IPAddress start, end;
            if( IPAddress.TryParse(startIp, out start) && IPAddress.TryParse(endIp, out end) )
                return BeginScan(IPAddress.Parse(startIp), IPAddress.Parse(endIp));
            else
                return false;
        }

		public bool BeginScan(IPAddress startIp, IPAddress endIp)
		{
            ClearErrors();
            InitScanThreading();
            
            /// Starts ping operations on a background thread.
	        /// Contains the starting and ending IP address for the pings.

			PingRange pr = new PingRange(startIp, endIp);
            if (pr.Valid == false)
                return false;

			Thread t = new Thread(new ParameterizedThreadStart(scanWorker_DoWork));
            t.Start(pr); //new PingRange(pr.StartRange, pr.EndRange));
            return true;
		}

        public void Scan(string startIp, string endIp)
        {
            _cancelSource = new CancellationSource();
            ClearErrors();
            InitScanThreading();

            /// Starts ping operations on a background thread.
            /// Contains the starting and ending IP address for the pings.

            PingRange pr = new PingRange(startIp, endIp);
            scanWorker_DoWork(pr);
        }

        const int _firstScanTimeOut = 10000;
        const int _remainingScanTimeOut = 20000;
        /// Loops through the IP address and does the pings.
        /// Start and end IP addresses to ping.
        private void scanWorker_DoWork(object o)
        {
            if (_cancelSource == null)
                _cancelSource = new CancellationSource();
            _cancelSource.AbortCancellation();
            //
            // The NetScan will perform the pings on separate threads
            //
            NetScan ns = new NetScan();
            ArrayList devFoundIpList = new ArrayList();
            PingRange range = (PingRange)o;
		    //bool scanComplete = false;
            byte[] start = range.StartRange.GetAddressBytes();
            byte[] end = range.EndRange.GetAddressBytes();
            scanStartTime = DateTime.Now;
            LogMessage("Scan started at " + scanStartTime.ToString());
            // Each thread that's spawned will be put in this list so that it's easy to
            // know when they've all completed.

            LinkedList<Thread> threads = new LinkedList<Thread>();

            NumScannedIp = 0;
            NumOfIpToScan = 0;
            // Loop through each octet in the IP address, and ping on a background thread.
            List<IPAddress> ipScanList = new List<IPAddress>();

            for (byte o0 = start[0]; o0 <= end[0]; o0++)
                for (byte o1 = start[1]; o1 <= end[1]; o1++)
                    for (byte o2 = start[2]; o2 <= end[2]; o2++)
                        for (byte o3 = start[3]; o3 <= end[3]; o3++)
                        {
                            NumOfIpToScan++;
                            ipScanList.Add(new IPAddress(new byte[] { o0, o1, o2, o3 }));
                        }

            NetScan.PingTimeout = _firstScanTimeOut;

            while (_cancelSource.IsCancelRequest == false)
            {
                //int count = 0;
                NumScannedIp = 0;
                threads.Clear();

                foreach (IPAddress a in ipScanList)
                {
                    if (_cancelSource.IsCancelRequest == true)
                        break;

                    socketBlock.WaitOne();
                    /*if (socketBlock.WaitOne(40000) == false)
                    {
                        _cancelSource.RequestCancellation();
                        LogMessage("Thread timeout");
                        //KillThreads(threads);
                        break;
                    }*/
                    Thread.Sleep(10);
                    Thread t = new Thread(new ParameterizedThreadStart(DoPing));
                    t.Priority = ThreadPriority.Lowest;
                    /*if (count >= ThreadCount)
                    {
                        count = 0;
                        Thread.Sleep(4000);
                    }
                    else
                        count++;*/
                    t.Start(a);
                    threads.AddLast(t);
                }

                // Wait until all the pings are done.

                foreach (Thread ln in threads)
                {
                    ln.Join();
                    /*if(ln.Join(60000) == false)
                    {
                        ln.Abort();
                        LogMessage("Thread timeout");
                        break;
                    }*/
                }
                NetScan.PingTimeout = _remainingScanTimeOut;
                if (MultiScanMode == false)
                    break;
            }
            // Raise an event saying the pings are done on the main UI thread.
            try
            {
                BuildMacDict();
                RaiseYNetScanComplete();
            }

            catch (InvalidOperationException ex)
            {
                AddError(ex.Message);
            }

            catch (NullReferenceException ex)
            {
                AddError(ex.Message);
            }
            LogMessage("Scan stopped at " + DateTime.Now.ToString());
        }

        private void KillThreads(LinkedList<Thread> threads)
        {
            foreach (Thread t in threads)
                t.Abort();
        }

            //private delegate void RaiseYNetScanCompleteHandler();

            /// Fire an event to the client on the same thread that they invoked this
            /// object on so that the UI can be safely updated.

            private void RaiseYNetScanComplete()
            {
                if (YNetScanComplete != null)
                    YNetScanComplete(this, new YNetScanCompletedEventArgs());
            }

            /// Perform the actual ping, and release the semaphore so that another thread
            /// can go.

            /// IP address to ping
            private void DoPing(object o)
            {                
                IPAddress ip = (IPAddress)o;
                PingReply pr;
                YDevice dev = null;

                try
                {
                    if (_cancelSource.IsCancelRequest == false)
                    {
                        pr = NetScan.Ping(ip, _cancelSource);
                        bool deviceExist = false;
                        lock (Devices)
                        {
                            deviceExist = Devices.ContainsKey(ip);
                        }
                        if (deviceExist)
                        {
                            lock (Devices)
                            {
                                dev = Devices[ip];
                            }
                            if (pr.Status == IPStatus.Success && dev.Modem.UpdateInfo() == true)
                            {
                                dev.Ping = pr.RoundtripTime.ToString();
                                dev.TxBitrate = dev.Modem.TxBitrate;
                                dev.RxBitrate = dev.Modem.RxBitrate;
                                dev.LowBitrate = Math.Min(dev.Modem.TxBitrate, dev.Modem.RxBitrate);
                                string server = dev.ServerName;
                                dev.UpdateObixData();
                                if (dev.ServerName != server)
                                {
                                    dev = YDevice.Create(ip);

                                    if (dev != null)
                                    {
                                        //Devices.Remove(ip);
                                        dev.Connected = true;
                                        dev.Ping = pr.RoundtripTime.ToString();
                                        Results(dev);
                                        LogMessage(dev.Ip.ToString() + " Detected with new server name");
                                    }
                                }
                                if (dev.Connected == false)
                                {
                                    dev.Connected = true;
                                    LogMessage(dev.Ip.ToString() + " Rejoined");
                                }
                            }
                            else
                            {
                                string message = ip.ToString()+ " Ping timeout";
                                AddError(message);
                                if (dev.Connected == true)
                                {
                                    dev.Connected = false;
                                    LogMessage(message);
                                }
                                dev.Ping = "TO";
                                dev.TxBitrate = 0;
                                dev.RxBitrate = 0;
                                dev.LowBitrate = 0;
                            }
                            //dev.ServerName =
                        }

                        if ((deviceExist == false) && (pr != null) && (pr.Status == IPStatus.Success) && (_cancelSource.IsCancelRequest == false))
                        {
                            Thread.Sleep(200);

                            if (deviceExist == false)
                            {
                                dev = YDevice.Create(ip);

                                if (dev != null)
                                {
                                    dev.Connected = true;
                                    dev.Ping = pr.RoundtripTime.ToString();
                                    Results(dev);
                                    LogMessage(dev.Ip.ToString() + " Detected");
                                }
                            }

                        }
                    }
                }
                catch (Exception ex)
                {
                    string error = ip.ToString() + ", " + ex.Message;
                    AddError(error);
                    LogMessage(error);
                }
                finally
                {
                    if( socketBlock != null )
                        socketBlock.Release();
                    lock (this)
                    {
                        NumScannedIp++;
                    }
                    if (YNetScanProgress != null)
                        YNetScanProgress(this, new YNetScanProgressEventArgs(ScanProgress));
                }
            }

            private void LogMessage(string message)
            {
                if (YNetScanLog != null)
                {
                    TimeSpan logRelativeTime = new TimeSpan(DateTime.Now.Ticks - scanStartTime.Ticks);
                    string messageAndTimeTag = message + ", +" + logRelativeTime.TotalSeconds.ToString("0.000")  + " sec";
                    YNetScanLog(this, new YNetScanLogEventArgs(messageAndTimeTag));
                }
            }

            void Results(YDevice dev)
            {
                YDevice old = null;
                lock (Devices)
                {
                    foreach(KeyValuePair<IPAddress,YDevice> kvp in Devices)
                    {
                        if (dev.Mac == kvp.Value.Mac)
                        {                            
                            old = kvp.Value;
                            break;
                        }
                        else if (dev.Ip == kvp.Value.Ip)
                        {
                            old = kvp.Value;
                            break;
                        }
                    }
                    if(old != null)
                        Devices.Remove(old.Ip);
                    // Add device to device dictionnaries
                    Devices.Add(IPAddress.Parse(dev.Modem.IpAddress), dev);
                }
                if (YDeviceDetected != null)
                    YDeviceDetected(this, new YNetDeviceDetectedEventArgs(dev,old));
            }
        /*
            void BeginRead(IPAddress[] devicesIp, List<ObixObject> objs)
            {
                socketBlock = new Semaphore(0, ThreadCount);
                socketBlock.Release(ThreadCount);
                List<YDevice> readDevices = new List<YDevice>();

                foreach (IPAddress i in devicesIp)
                {
                    if (Devices.ContainsKey(i))
                        Devices[i].BatchInObjects = objs;
                }
                /// Starts ping operations on a background thread.
                /// Contains the starting and ending IP address for the pings.

                Thread t = new Thread(new ParameterizedThreadStart(readWorker_DoWork));
                t.Start(readDevices);

            }*/

            /// Loops through the IP address and does the pings.
            /// Start and end IP addresses to ping.
            private void readWorker_DoWork(object o)
            {
                ClearErrors();
                List<YDevice> readDevices = (List<YDevice>) o;

                // Each thread that's spawned will be put in this list so that it's easy to
                // know when they've all completed.

                LinkedList<Thread> threads = new LinkedList<Thread>();

                // Loop through each octet in the IP address, and ping on a background thread.

                foreach(YDevice y in readDevices)
                {
                    socketBlock.WaitOne();
                    Thread t = new Thread(new ParameterizedThreadStart(DoRead));
                    t.Start(y);
                    threads.AddLast(t);
                }

                // Wait until all the pings are done.

                foreach (Thread ln in threads)
                {
                    ln.Join();
                }

                // Raise an event saying the pings are done on the main UI thread.
                try
                {
                    RaiseYNetScanComplete();
                }

                catch (InvalidOperationException ex)
                {
                    AddError(ex.Message);
                }

                catch (NullReferenceException ex)
                {
                    AddError(ex.Message);
                }

            }

            private void DoRead(object o)
            {
                YDevice y = (YDevice) o;
                PingReply pr;
                IPAddress ip = y.Ip;

                try
                {
                    pr = NetScan.Ping(ip);

                    if (pr.Status == IPStatus.Success)
                    {
                        //Console.WriteLine(ip.ToString() + " ping " + pr.Address.ToString());
                        List<ObixObject> obObj = y.Read(new string[] { "/obix/about/serverName" });
                        if (y.ErrorMessage != null && y.ErrorMessage != "")
                            AddError(y.ErrorMessage);
                        if (obObj != null)
                            ReadResults(y);
                    }
                    else
                        AddError("Error connecting to " + ip.ToString());
                }
                catch (Exception ex)
                {
                    AddError(ex.Message);
                }
            }
        
            void ReadResults(YDevice dev)
            {
                if (YDeviceReadComplete != null)
                {
                    // Add device to device dictionnaries
                    //Devices.Add(IPAddress.Parse(dev.Modem.IpAddress), dev);
                    YDeviceReadComplete(this, new YNetDeviceReadEventArgs(dev));
                }
            }
        
           // private delegate void RaiseYNetReadCompleteHandler();

            /// Fire an event to the client on the same thread that they invoked this
            /// object on so that the UI can be safely updated.

            private void RaiseYNetReadComplete()
            {
                if (YNetReadComplete != null)
                    YNetReadComplete(this, new YNetReadCompletedEventArgs());
            }

            public void BeginWrite(List<YDevice> devices, ObixEntry[] entries)
            {
                ClearErrors();
                socketBlock = new Semaphore(0, ThreadCount);
                socketBlock.Release(ThreadCount);

                if( entries != null)
                    foreach(YDevice y in devices)
                        y.ObixEntries = entries;

                /// Starts write operations on a background thread.
                writeWorker_DoWork(devices);
                //Thread t = new Thread(new ParameterizedThreadStart(writeWorker_DoWork));
                //t.Start(devices);

            }

			public void Write(List<YDevice> devices, ObixEntry[] entries)
			{
                ClearErrors();
				socketBlock = new Semaphore(0, ThreadCount);
				socketBlock.Release(ThreadCount);

				foreach (YDevice y in devices)
					y.ObixEntries = entries;

				/// Starts write operations on a background thread.

				writeWorker_DoWork(devices);

			}
            /// Loops through the IP address and does the pings.
            /// Start and end IP addresses to ping.
            private void writeWorker_DoWork(object o)
            {
                List<YDevice> writeDevices = (List<YDevice>)o;

                // Each thread that's spawned will be put in this list so that it's easy to
                // know when they've all completed.

                LinkedList<Thread> threads = new LinkedList<Thread>();

                // Loop through each octet in the IP address, and ping on a background thread.

                foreach (YDevice y in writeDevices)
                {
                    socketBlock.WaitOne();
                    Thread t = new Thread(new ParameterizedThreadStart(DoWrite));
                    t.Start(y);
                    threads.AddLast(t);
                }

                // Wait until all the pings are done.

                foreach (Thread ln in threads)
                {
                    ln.Join();
                }

                // Raise an event saying the pings are done on the main UI thread.
                try
                {
                    RaiseYNetWriteComplete();
                }

                catch (InvalidOperationException)
                {
                }

                catch (NullReferenceException)
                {
                }

            }

            private void DoWrite(object o)
            {
                YDevice y = (YDevice)o;
                //PingReply pr;
                //IPAddress ip = y.Ip;

                try
                {
                   // pr = NetScan.Ping(ip);

                   // if (pr.Status == IPStatus.Success)
                    {
                        List<ObixObject> obObj = y.Write(y.ObixEntries);
                        
                        if (y.ErrorMessage != null && y.ErrorMessage != "")
                            AddError(y.ErrorMessage);

                        //if (obObj != null && obObj.Count != y.ObixEntries.Length)
                        //    AddError("Error while writing to " + ip.ToString());

                        if (obObj != null)
                        {
                           WriteResults(y);
                        }
                    }
                    //else
                    //    AddError("Error connecting to " + ip.ToString());
                }
                catch (Exception ex)
                {
                    AddError(ex.Message);
                }
            }

            void WriteResults(YDevice dev)
            {
                if (YDeviceWriteComplete != null)
                {
                    // Add device to device dictionnaries
                    //Devices.Add(IPAddress.Parse(dev.Modem.IpAddress), dev);
                    YDeviceWriteComplete(this, new YNetDeviceWriteEventArgs(dev));
                }
            }

            private void RaiseYNetWriteComplete()
            {
                if (YNetWriteComplete != null)
                    YNetWriteComplete(this, new YNetWriteCompletedEventArgs());
            }


            public List<YDevice> GetDeviceList(string server)
            {
                List<YDevice> devs = new List<YDevice>();

                if( server == "Modem" )
                {
                    foreach (KeyValuePair<IPAddress, YDevice> kv in Devices)
                    {
                        if (kv.Value.ServerName != null && kv.Value.ServerName != "Rx Master")
                            devs.Add(kv.Value);
                    }
                }
                else
                {
                    foreach (KeyValuePair<IPAddress, YDevice> kv in Devices)
                    {
                        if (kv.Value.ServerName != null && kv.Value.ServerName.CompareTo(server) == 0)
                            devs.Add(kv.Value);
                    }
                }
                return devs;
            }


            public void BeginUpgrade(List<YDevice> devices, string filename)
            {
                updateProgress = 0;

                socketBlock = new Semaphore(0, ThreadCount);
                socketBlock.Release(ThreadCount);
                string[] programLines = File.ReadAllLines(filename);
                updateProgressGoal = programLines.Length * devices.Count;

                foreach (YDevice y in devices)
                {
                    if (filename.Contains(y.ServerName) == false)
                        throw new Exception("Some of the selected devices are not of the same type as the update file");
                }

                foreach (YDevice y in devices)
                {
                    y.UpgradeLines = programLines;
                    y.YDeviceUpgradeProgressed -= UpgradeProgressed;                    
                    y.YDeviceUpgradeProgressed += UpgradeProgressed;                    
                }
                /// Starts write operations on a background thread.

                Thread t = new Thread(new ParameterizedThreadStart(upgradeWorker_DoWork));
                t.Start(devices);

            }

            /// Loops through the IP address and does the pings.
            /// Start and end IP addresses to ping.
            private void upgradeWorker_DoWork(object o)
            {
                List<YDevice> upgradeDevices = (List<YDevice>)o;

                // Each thread that's spawned will be put in this list so that it's easy to
                // know when they've all completed.

                LinkedList<Thread> threads = new LinkedList<Thread>();

                // Loop through each octet in the IP address, and ping on a background thread.

                foreach (YDevice y in upgradeDevices)
                {
                    socketBlock.WaitOne();
                    Thread t = new Thread(new ParameterizedThreadStart(DoUpgrade));
                    t.Start(y);
                    threads.AddLast(t);
                }

                // Wait until all the pings are done.

                foreach (Thread ln in threads)
                {
                    ln.Join();
                }

                // Raise an event saying the pings are done on the main UI thread.
                try
                {
                    RaiseYNetUpgradeComplete();
                }

                catch (InvalidOperationException)
                {
                }

                catch (NullReferenceException)
                {
                }

            }

            private void DoUpgrade(object o)
            {
                YDevice y = (YDevice)o;
                //PingReply pr;
                IPAddress ip = y.Ip;

                try
                {
                    //pr = NetScan.Ping(ip);

                    //if (pr.Status == IPStatus.Success)
                    {
                        if (y.Upgrade() == true)
                        {
                            try
                            {
                                UpgradeResults(y, true);
                            }
                            catch 
                            {
                            }
                        }
                        else
                        {
                            UpgradeResults(y, false);
                        }
                    }
                }
                catch
                {
                    UpgradeResults(y, false);
                }
            }

            void UpgradeResults(YDevice dev, bool success)
            {
                if (YNetUpgradeProgress != null)
                {
                    // Add device to device dictionnaries
                    //Devices.Add(IPAddress.Parse(dev.Modem.IpAddress), dev);
                    YNetUpgradeProgressEventArgs progEventArg = new YNetUpgradeProgressEventArgs(dev);
                    if (success)
                    {
                        float prog = updateProgress * 100;
                        prog /= updateProgressGoal;
                        progEventArg.Percent = (int) prog;
                    }
                    else
                        progEventArg.Percent = -1;
					progEventArg.Success = success;
                    YNetUpgradeProgress(this, progEventArg);
                }
            }

            private void RaiseYNetUpgradeComplete()
            {
                if (YNetUpgradeComplete != null)
                    YNetUpgradeComplete(this, new YNetUpgradeCompletedEventArgs());
            }

            private void UpgradeProgressed(object sender, YDeviceUpdateProgressedEventArgs e)
            {
                updateProgress += e.LinesUpgraded;
                UpgradeResults((YDevice)sender, true);
                /*
                this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (ThreadStart)delegate()
                {
                    //YNetManager yman = (YNetManager)sender;
                    //y1gUpgradeProgressBar.Value = e.Percent;
                });*/
            }
        /*
            public List<YDevice> GetDeviceList(string server)
            {
                List<YDevice> devs = new List<YDevice>();

                foreach (KeyValuePair<IPAddress, YDevice> kv in Devices)
                {
                    if (kv.Value.ServerName != null && kv.Value.ServerName.CompareTo(server) == 0)
                        devs.Add(kv.Value);
                }

                return devs;
            }*/

        private void ResetModems(List<YDevice> devices)
        {
            int level = -1;

            foreach (YDevice dev in devices)
            {
                if (dev.Modem.Level > level)
                    level = dev.Modem.Level;
            }

            while(level >= 0)
            {
                foreach (YDevice dev in devices)
                {
                    if (dev.Modem.Level >= level)
                    {
                        dev.Modem.Reset();
                        //devices.Remove(dev);
                    }
                }
                level--;
            }
        }

        /*
        public bool BeginMACChange(List<YDevice> devices)
        {
            updateProgress = 0;

            socketBlock = new Semaphore(0, ThreadCount);
            socketBlock.Release(ThreadCount);
            updateProgressGoal = 2097152 * devices.Count;// / 10;
            updateProgress = 0;


            foreach (YDevice y in devices)
            {
                //y.Modem.ServerIp = serverIp;
                //y.Modem.FirmwareFilename = safeFilename;
                y.Modem.ModemUpgradeProgressed += ModemUpgradeProgressed;
            }
            /// Starts write operations on a background thread.

            Thread t = new Thread(new ParameterizedThreadStart(ModemMACChangeWorker_DoWork));
            t.Start(devices);

            return true;
        }
        */

        public bool BeginModemUpgrade(List<YDevice> devices, string serverIp, string filename)
        {
            string aPath = System.Reflection.Assembly.GetExecutingAssembly().Location;
            string appPath = System.IO.Path.GetDirectoryName(aPath);
            string safeFilename = System.IO.Path.GetFileName(filename);

            //string ftpFileName = Path.GetFileName(filename);
            //string ftpDestPath = appPath + "\\" + ftpFileName;
            //File.Delete(ftpDestPath);
            //File.Copy(filename, ftpDestPath);
            try
            {
                Process proc = new Process();
                proc.StartInfo.FileName = appPath + "\\tftpd32.exe";
                string keyPath;
                RegistryKey key = Registry.LocalMachine.OpenSubKey("Software\\TFTPD32", true);
                if (key == null)
                {
                    key = Registry.LocalMachine.OpenSubKey("Software\\Wow6432Node\\TFTPD32", true);
                    keyPath = "Software\\Wow6432Node\\TFTPD32";
                }
                else
                {
                    keyPath = "Software\\TFTPD32";
                }

                // If the return value is null, the key doesn't exist
                if (key == null)
                {
                    proc.Start();
                    Thread.Sleep(2000);
                    proc.CloseMainWindow();
                    // The key doesn't exist; create it / open it
                    key = Registry.LocalMachine.OpenSubKey("Software\\TFTPD32", true);
                    if (key == null)
                    {
                        return false;
                    }
                }

                if (key.GetValue("BaseDirectory") != null)
                { // wrtie to the TFTPD32 registry
                    key.SetValue("BaseDirectory", Path.GetDirectoryName(filename));//ftpDestPath);
                }

                proc.Start();
            }
            catch (Exception ex)
            {
                MessageBox.Show("Open the Program Tftpd32, and select the Current Directory to where the update file is located", "Action Required", MessageBoxButtons.OK);
            }
            updateProgress = 0;

            socketBlock = new Semaphore(0, ThreadCount);
            socketBlock.Release(ThreadCount);
            //string[] programLines = File.ReadAllLines(filename);
            //updateProgressGoal = 2097152 * devices.Count;// / 10;
            updateProgressGoal = 749492 * devices.Count;
            updateProgress = 0;


            foreach (YDevice y in devices)
            {
                y.Modem.ServerIp = serverIp;
                y.Modem.FirmwareFilename = safeFilename;
                y.Modem.ModemUpgradeProgressed -= ModemUpgradeProgressed;
                y.Modem.ModemUpgradeProgressed += ModemUpgradeProgressed;
            }
            /// Starts write operations on a background thread.

            Thread t = new Thread(new ParameterizedThreadStart(ModemUpgradeWorker_DoWork));
            t.Start(devices);

            return true;
        }


		/// Loops through the IP address and does the pings.
		/// Start and end IP addresses to ping.
		private void ModemUpgradeWorker_DoWork(object o)
		{
			List<YDevice> upgradeDevices = (List<YDevice>)o;

			// Each thread that's spawned will be put in this list so that it's easy to
			// know when they've all completed.

			LinkedList<Thread> threads = new LinkedList<Thread>();

			// Loop through each octet in the IP address, and ping on a background thread.

			foreach (YDevice y in upgradeDevices)
			{
				socketBlock.WaitOne();
                Thread t = new Thread(new ParameterizedThreadStart(DoModemUpgrade));
				t.Start(y);
				threads.AddLast(t);
			}

			// Wait until all the pings are done.

			foreach (Thread ln in threads)
			{
				ln.Join();
			}

			// Raise an event saying the pings are done on the main UI thread.
			try
			{
				RaiseYNetModemUpgradeComplete();
                
                DialogResult dr = MessageBox.Show("Do you want to reset the upgraded devices now?", "Action Required", MessageBoxButtons.YesNo);
                if (dr == DialogResult.Yes)
                {
                    ResetModems(upgradeDevices);
                }
                else
                {
                    MessageBox.Show("Use reset button later to complete the Modem upgrade", "Action Required", MessageBoxButtons.OK);
                }
                
            }

			catch (InvalidOperationException)
			{
			}

			catch (NullReferenceException)
			{
			}

		}
        /*
        /// Loops through the IP address and does the pings.
        /// Start and end IP addresses to ping.
        private void ModemMACChangeWorker_DoWork(object o)
        {
            List<YDevice> upgradeDevices = (List<YDevice>)o;

            // Each thread that's spawned will be put in this list so that it's easy to
            // know when they've all completed.

            LinkedList<Thread> threads = new LinkedList<Thread>();

            // Loop through each octet in the IP address, and ping on a background thread.

            foreach (YDevice y in upgradeDevices)
            {
                socketBlock.WaitOne();
                Thread t = new Thread(new ParameterizedThreadStart(DoMACChange));
                t.Start(y);
                threads.AddLast(t);
            }

            // Wait until all the pings are done.

            foreach (Thread ln in threads)
            {
                ln.Join();
            }

            // Raise an event saying the pings are done on the main UI thread.
            try
            {
                //RaiseYNetModemUpgradeComplete();
                ResetModems(upgradeDevices);
            }

            catch (InvalidOperationException)
            {
            }

            catch (NullReferenceException)
            {
            }

        }*/
        
		private void DoModemUpgrade(object o)
		{
			YDevice y = (YDevice)o;
			//PingReply pr;
			IPAddress ip = y.Ip;

			//pr = NetScan.Ping(ip);

			//if (pr.Status == IPStatus.Success)
			{
				if (y.Modem.UpdateFirmware() == true)
				{
					try
					{
						UpgradeResults(y, true);
					}
                    catch (InvalidOperationException)
                    {
                        UpgradeResults(y, true);
                    }
					/*catch (InvalidOperationException)
					{
                        UpgradeResults(y, true);
					}
					catch (NullReferenceException)
					{
                        UpgradeResults(y, true);
					}*/
				}
				else
				{
					UpgradeResults(y, false);
				}
			}

		}

        public bool DoMACChange(YDevice y)
        {
            //YDevice y = (YDevice)o;
           // PingReply pr;
            IPAddress ip = y.Ip;

           // pr = NetScan.Ping(ip);

           // if (pr.Status == IPStatus.Success)
           // {
                if (y.Modem.ChangeMAC() == true)
                {
                    Thread.Sleep(400);
                    if( y.Modem.Reset() == true )
                        return true;
                }
            //}

            return false;
        }
        
        void ModemUpgradeResults(YDevice dev, bool success)
        {
            if (YNetUpgradeProgress != null)
            {
                // Add device to device dictionnaries
                //Devices.Add(IPAddress.Parse(dev.Modem.IpAddress), dev);
                YNetUpgradeProgressEventArgs progEventArg = new YNetUpgradeProgressEventArgs(dev);
                if (success)
                    progEventArg.Percent = (updateProgress * 100) / updateProgressGoal;
                else
                    progEventArg.Percent = -1;
                progEventArg.Success = success;
                YNetUpgradeProgress(this, progEventArg);
            }
        }

        private void RaiseYNetModemUpgradeComplete()
        {
            if (YNetUpgradeComplete != null)
                YNetUpgradeComplete(this, new YNetUpgradeCompletedEventArgs());
        }

        private void ModemUpgradeProgressed(object sender, ModemUpdateProgressedEventArgs e)
        {
            updateProgress += e.BytesDone;
            DS2Modem mod = (DS2Modem)sender;
            YDevice dev = Devices[IPAddress.Parse(mod.IpAddress)];
            ModemUpgradeResults(dev, true);
        }

	}
}
